<script setup>
import { ref, computed, onMounted } from 'vue'

const props = defineProps({
  data: {
    type: Array,
    default() {
      return [
        {
          title: '菜单1',
          handleClick: () => {},
          children: [
            {
              title: '菜单1-1',
              handleClick: () => {}
            },
            {
              title: '菜单1-2',
              handleClick: () => {}
            }
          ]
        },
        {
          title: '菜单2',
          handleClick: () => {},
          children: [
            {
              title: '菜单2-1',
              handleClick: () => {}
            },
            {
              title: '菜单2-2',
              handleClick: () => {}
            }
          ]
        }
      ]
    }
  }
})

const emit = defineEmits(['click'])

const PI_2 = 2 * Math.PI
let rongQi = ref()
let center = ref()
let centerWidth = ref(80)
let centerWidthStr = computed(() => `${centerWidth.value}px`)
let centerRadius = computed(() => centerWidth.value / 2)
let itemWidth = ref(70) // 菜单项宽
let maxAngle = ref(PI_2) // 菜单整体的最大角度
const showHideMap = new WeakMap()
const fontSize = ref('13px')
// 显示菜单项
const showHideElements = (targetEl) => {
  const show = !showHideMap.get(targetEl)
  const { level, index, pIndex } = targetEl.dataset
  const pIndexArr = pIndex.split('-')

  showHideMap.set(targetEl, show)

  const allItems = rongQi.value.querySelectorAll(`div[data-level]`)
  allItems.forEach((e) => {
    if (pIndexArr.includes(e.dataset.index) || targetEl === e) return
    if (e.dataset.level > Number(level)) e.classList.remove('show')
    e.classList.remove('active')
    showHideMap.set(e, false)
  })

  if (show) {
    targetEl.classList.add('active')

    const children = rongQi.value.querySelectorAll(
      `div[data-level='${+level + 1}'][data-p-index*='${index}']`
    )

    children.forEach((e) => e.classList.add('show'))
  } else {
    targetEl.classList.remove('active')
  }
}

const generateIndex = () => {
  return Math.random().toString(16).replace('.', '')
}
// clip-path使子元素不能显示，所有菜单项放在一起
let creatMenu = (data, level, parent) => {
  const perAngle = maxAngle.value / data.length // 每个扇形弧度
  const angle = perAngle >= PI_2 ? PI_2 - 0.001 : perAngle
  const largeArcFlag = data.length == 1 ? 1 : 0

  for (let i = 0, d = data; i < d.length; i++) {
    let radius = centerRadius.value + itemWidth.value * level // 菜单项半径
    let startAngle = i * angle // 开始角度
    let endAngle = (i + 1) * angle // 结束角度
    let lineX = radius + Math.sin(startAngle) * radius
    let lineY = radius - Math.cos(startAngle) * radius
    let pointX = radius + Math.sin(endAngle) * radius
    let pointY = radius - Math.cos(endAngle) * radius
    let div = document.createElement('div')
    div.classList.add('menu-item')
    div.dataset.level = +level
    div.dataset.index = generateIndex()
    div.dataset.pIndex = parent.dataset.pIndex
      ? `${parent.dataset.pIndex}-${parent.dataset.index}`
      : `${parent.dataset.index}`
    div.style.setProperty('width', `${radius * 2}px`)
    div.style.setProperty('height', `${radius * 2}px`)
    div.style.setProperty('z-index', -level)
    div.style.setProperty(
      'clip-path',
      `path("M ${radius} ${radius} L ${lineX} ${lineY} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${pointX} ${pointY} z")`
    )

    const bgEl = document.createElement('div')
    bgEl.className = 'item-bg'
    if (level % 2 == 0) bgEl.classList.add('bg-even')
    else bgEl.classList.add('bg-odd')
    endAngle = (i + 1) * angle - 0.01 // 结束角度
    lineX = radius + Math.sin(startAngle) * radius
    lineY = radius - Math.cos(startAngle) * radius
    pointX = radius + Math.sin(endAngle) * radius
    pointY = radius - Math.cos(endAngle) * radius
    bgEl.style.setProperty('width', `${radius * 2}px`)
    bgEl.style.setProperty('height', `${radius * 2}px`)
    bgEl.style.setProperty(
      'clip-path',
      `path("M ${radius} ${radius} L ${lineX} ${lineY} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${pointX} ${pointY} z")`
    )
    div.appendChild(bgEl)

    let textAngle = (i + 1 / 2) * angle
    let textRadius = radius - itemWidth.value / 2
    let title = document.createElement('div')
    title.style.left = radius + Math.sin(textAngle) * textRadius + 'px'
    title.style.top = radius - Math.cos(textAngle) * textRadius + 'px'
    title.style.setProperty('position', 'absolute')
    title.style.textAlign = 'center'
    // 旋转y轴为0，文本初始角度90度
    title.style.transform = `translate(-50%, -50%) rotate(${-Math.PI / 2 + textAngle}rad)`
    let text = document.createElement('div')
    let textRotate = textAngle > Math.PI ? Math.PI : 0
    text.style.display = 'inline-block'
    text.innerText = d[i].title
    text.style.width = `${itemWidth.value - 10}px`
    // 左侧的倒文字镜像一下
    text.style.transform = `rotateY(${textRotate}rad) rotateX(${textRotate}rad)`

    title.appendChild(text)
    div.appendChild(title)

    div.addEventListener('pointerdown', () => {
      d[i].handleClick?.()
      emit('click', d[i].title)
    })
    // 展示选中的菜单
    div.addEventListener('pointerdown', () => showHideElements(div))

    rongQi.value.insertAdjacentElement('afterbegin', div)

    if (d[i].children) creatMenu(d[i].children, level + 1, div)
  }
}
// 拖拽
const elementDraggale = (targetEl, dragEl) => {
  let transform = {
    offsetX: 0,
    offsetY: 0
  }

  const onMousedown = (e) => {
    const downX = e.clientX
    const downY = e.clientY
    const { offsetX, offsetY } = transform

    const targetRect = targetEl.getBoundingClientRect()
    const targetLeft = targetRect.left
    const targetTop = targetRect.top
    const targetWidth = targetRect.width
    const targetHeight = targetRect.height

    const clientWidth = document.documentElement.clientWidth
    const clientHeight = document.documentElement.clientHeight

    const minLeft = -targetLeft + offsetX
    const minTop = -targetTop + offsetY
    const maxLeft = clientWidth - targetLeft - targetWidth + offsetX
    const maxTop = clientHeight - targetTop - targetHeight + offsetY

    const onMousemove = (e) => {
      let moveX = offsetX + e.clientX - downX
      let moveY = offsetY + e.clientY - downY

      moveX = Math.min(Math.max(moveX, minLeft), maxLeft)
      moveY = Math.min(Math.max(moveY, minTop), maxTop)

      transform = {
        offsetX: moveX,
        offsetY: moveY
      }

      if (targetEl) {
        targetEl.style.transform = `translate(${moveX}px, ${moveY}px)`
      }
    }

    const onMouseup = () => {
      document.removeEventListener('mousemove', onMousemove)
      document.removeEventListener('mouseup', onMouseup)
    }

    document.addEventListener('mousemove', onMousemove)
    document.addEventListener('mouseup', onMouseup)
  }

  dragEl.addEventListener('mousedown', onMousedown)
}

const init = () => {
  elementDraggale(rongQi.value, center.value)

  const level = '0'
  const index = generateIndex()
  center.value.dataset.level = level
  center.value.dataset.index = index
  center.value.dataset.pIndex = ''
  creatMenu(props.data, +level + 1, center.value)

  center.value.addEventListener('pointerdown', (e) => showHideElements(center.value))
}

onMounted(() => {
  init()
})
</script>

<template>
  <div ref="rongQi" class="rong-qi">
    <div ref="center" class="center" draggable="false" @dblclick="emit('click', 'center')">
      <slot name="center" draggable="false">菜单</slot>
    </div>
  </div>
</template>

<style scoped>
.rong-qi {
  --color: rgb(120 240 255 / 1);
  --border-color: rgb(80 160 255 / 1);
  --bg-center: rgb(30 60 120 / 1);
  --bg-odd: conic-gradient(rgb(40 80 160 / 0.2), rgb(20 40 80 / 0.2)),
    radial-gradient(rgb(40 80 160 / 1), rgb(20 40 80 / 1));
  --bg-even: conic-gradient(rgb(20 40 80 / 0.2), rgb(40 80 160 / 0.2)),
    radial-gradient(rgb(20 40 80 / 1), rgb(40 80 160 / 1));
  font-size: v-bind(fontSize);
  color: var(--color);
  cursor: pointer;
  position: fixed;
  width: 0;
  height: 0;
  top: 50%;
  left: 50%;
  z-index: 999999999;
  transform: translate(-50%, -50%);
  user-select: none;
  filter: drop-shadow(0 0 10px rgb(50 100 200 / 1));
}
.center {
  box-sizing: border-box;
  font-weight: bold;
  cursor: move;
  position: absolute;
  left: 50%;
  top: 50%;
  width: v-bind(centerWidthStr);
  height: v-bind(centerWidthStr);
  border: 1px solid var(--border-color);
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background: var(--bg-center);
  display: flex;
  justify-content: center;
  align-items: center;
}
.center:hover {
  filter: brightness(1.5);
}
.center > :deep(*) {
  width: 90%;
  height: 90%;
}
:deep(.active) {
  filter: brightness(1.5);
}
:deep(.menu-item) {
  color: var(--color);
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%) scale(0);
  /* transition all clip-path 有问题 */
  transition: transform 0.5s ease;
  background: var(--border-color);
}
:deep(.menu-item:hover) {
  filter: brightness(1.5);
}
:deep(.item-bg) {
  width: 100%;
  height: 100%;
}
:deep(.bg-odd) {
  background: var(--bg-odd);
}
:deep(.bg-even) {
  background: var(--bg-even);
}
:deep(.show) {
  transform: translate(-50%, -50%) scale(1);
}
</style>
